package com.gitee.apanlh.util.algorithm.encrypt.asymmetric;

import com.gitee.apanlh.exp.algorithm.AsymmetricException;
import com.gitee.apanlh.util.algorithm.ECKeyUtils;
import com.gitee.apanlh.util.algorithm.KeyUtils;
import com.gitee.apanlh.util.algorithm.encrypt.Encrypt;
import com.gitee.apanlh.util.base.ChooseOr;
import com.gitee.apanlh.util.base.Empty;
import com.gitee.apanlh.util.check.CheckImport;
import com.gitee.apanlh.util.check.CheckLibrary;
import com.gitee.apanlh.util.encode.Base64Type;
import com.gitee.apanlh.util.encode.Base64Utils;
import com.gitee.apanlh.util.encode.HexUtils;
import com.gitee.apanlh.util.encode.StrEncodeUtils;
import com.gitee.apanlh.util.valid.Assert;
import com.gitee.apanlh.util.valid.ValidParam;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;

/**
 * 	SM2国密算法抽象类
 * 	<br>默认算法为C1C3C2，默认曲线sm2p256v1
 * 	<br>需要依赖BouncyCastleProvider库
 *  <br>C1：表示椭圆曲线上的一个点，用于进行签名或者加密过程中的一些运算
 *  <br>C2：表示加密的结果，即加密后的数据
 * 	<br>C3：表示对数据的签名值，用于验证数据的完整性，不是加密过程中的一部分
 * 
 * 	#mark 需要验证OPEN SSH EC方法，签名方法
 * 	@author Pan
 */
public abstract class SM2AsymmetricAbstract implements Encrypt, AsymmetricEncode {
	
	/** 静态Provider */
	static Provider bouncyCastleProvider = null;
	
	/** 默认曲线 */
	public static final String CURVE_NAME = "sm2p256v1";
	public static final X9ECParameters EC_PARAMETERS = GMNamedCurves.getByName(CURVE_NAME);
	public static final ECDomainParameters EC_DOMAIN_PARAMETERS = new ECDomainParameters(EC_PARAMETERS.getCurve(), EC_PARAMETERS.getG(), EC_PARAMETERS.getN(), EC_PARAMETERS.getH());
	
	static {
		CheckImport.library(CheckLibrary.BOUNCY_CASTLE);
		bouncyCastleProvider = new BouncyCastleProvider();
		Security.addProvider(bouncyCastleProvider);
	}
	
	/** 模式 */
	private SM2Mode sm2Mode;
	/** 加密引擎 */
	private SM2Engine encryptEngine;
	/** 解密引擎 */
	private SM2Engine decryptEngine;
	/** 公钥 */
	private AsymmetricKeyParameter publicKey;
	/** 私钥 */
	private AsymmetricKeyParameter privateKey;
	
	/**
	 * 	默认构造函数
	 * 	
	 * 	@author Pan
	 */
	SM2AsymmetricAbstract() {
		super();
	}
	
	/**
	 * 	构造函数-初始化密钥对
	 * 	<br>自定义SM2模式推荐默认C1C3C2模式
	 * 	
	 * 	@author Pan
	 * 	@param 	sm2Mode	SM2模式
	 */
	SM2AsymmetricAbstract(SM2Mode sm2Mode) {
		this.sm2Mode = sm2Mode;
		initializeKeys(null, null);
	}
	
	/**	
	 * 	构造函数-将解析公钥及私钥并还原
	 * 	<br>自定义模式
	 * 	<br>注意:字符串仅限原有公钥或密钥经过Base64编码或十六进制编码
	 * 	<br>根据加密或解密传递对应值，任意传递一个即可
	 * 	
	 * 	@author Pan
	 * 	@param 	publicKey	公钥
	 * 	@param 	privateKey	私钥
	 * 	@param 	sm2Mode		SM2模式
	 */
	SM2AsymmetricAbstract(String publicKey, String privateKey, SM2Mode sm2Mode) {
		Assert.isFalse(ValidParam.isNull(publicKey) && ValidParam.isNull(privateKey));
		
		this.sm2Mode = sm2Mode;
		ChooseOr.create(!ValidParam.isEmpty(publicKey), () -> this.publicKey = ECKeyUtils.generatePublicKey(KeyUtils.decode(publicKey), EC_DOMAIN_PARAMETERS))
				.orElseIf(!ValidParam.isEmpty(privateKey), () -> this.privateKey = ECKeyUtils.generatePrivateKey(KeyUtils.decode(privateKey), EC_DOMAIN_PARAMETERS));
	}
	
	/**	
	 * 	构造函数-将解析公钥及私钥并还原
	 * 	<br>自定义模式
	 * 	<br>根据加密或解密传递对应值，任意传递一个即可
	 * 	<pre>支持格式:
	 * 	原始Q值,D值方式
	 * 	X509(PublicKey,PrivateKey)
	 * 	OpenSSH方式
	 * 	</pre>
	 * 	
	 * 	@author Pan
	 * 	@param 	publicKey	公钥
	 * 	@param 	privateKey	私钥
	 * 	@param 	sm2Mode		SM2模式
	 */
	SM2AsymmetricAbstract(byte[] publicKey, byte[] privateKey, SM2Mode sm2Mode) {
		Assert.isFalse(ValidParam.isNull(publicKey) && ValidParam.isNull(privateKey));
		
		this.sm2Mode = sm2Mode;
		initializeKeys(publicKey, privateKey);
	}
	
	/**	
	 * 	构造函数-加载公钥及私钥
	 * 	<br>自定义模式
	 * 	<br>根据加密或解密传递对应值，任意传递一个即可
	 * 	
	 * 	@author Pan
	 * 	@param 	publicKey	公钥
	 * 	@param 	privateKey	私钥
	 * 	@param 	sm2Mode		SM2模式
	 */
	SM2AsymmetricAbstract(ECPublicKeyParameters publicKey, ECPrivateKeyParameters privateKey, SM2Mode sm2Mode) {
		Assert.isFalse(ValidParam.isNull(publicKey) && ValidParam.isNull(privateKey));
		
		this.sm2Mode = sm2Mode;
		this.publicKey = publicKey;
		this.privateKey = privateKey;
	}
	
	/**	
	 * 	构造函数-将解析公钥及私钥并还原
	 * 	<br>自定义模式
	 * 	<br>根据加密或解密传递对应值，任意传递一个即可
	 * 	<br>X509(PublicKey,PrivateKey)
	 * 	
	 * 	@author Pan
	 * 	@param 	publicKey	公钥
	 * 	@param 	privateKey	私钥
	 * 	@param 	sm2Mode		SM2模式
	 */
	SM2AsymmetricAbstract(PublicKey publicKey, PrivateKey privateKey, SM2Mode sm2Mode) {
		Assert.isFalse(ValidParam.isNull(publicKey) && ValidParam.isNull(privateKey));
		
		this.sm2Mode = sm2Mode;
		ChooseOr.create(ValidParam.isNotNull(publicKey), () -> this.publicKey = ECKeyUtils.generatePublicKey(publicKey))
				.orElseIf(ValidParam.isNotNull(privateKey), () -> this.privateKey = ECKeyUtils.generatePrivateKey(privateKey));
	}
	
	/**
	 * 	构造函数-根据公钥Q值或私钥D值来进行还原
	 * 	<br>自定义模式
	 * 	<br>根据加密或解密传递对应值，任意传递一个即可
	 * 
	 * 	@author Pan
	 * 	@param 	q		公钥Q值
	 * 	@param 	d		私钥D值
	 * 	@param 	sm2Mode	SM2模式
	 */
	SM2AsymmetricAbstract(ECPoint q, BigInteger d, SM2Mode sm2Mode) {
		Assert.isFalse(ValidParam.isNull(q) && ValidParam.isNull(d));
		
		this.sm2Mode = sm2Mode;
		ChooseOr.create(ValidParam.isNotNull(q), () -> this.publicKey = ECKeyUtils.generatePublicKey(q, EC_DOMAIN_PARAMETERS))
				.orElseIf(ValidParam.isNotNull(d), () -> this.privateKey = getPrivateKey(d.toByteArray()));
	}

	/**	
	 * 	初始化SM2
	 * 	
	 * 	@author Pan
	 * 	@param 	isEncrypt	true加密模式，false解密
	 * 	@return SM2Engine
	 */
	private SM2Engine initEngine(boolean isEncrypt) {
		try {
			SM2Engine engine = new SM2Engine(this.sm2Mode.getMode());
			CipherParameters cipher = ChooseOr.create(isEncrypt, () -> (CipherParameters) new ParametersWithRandom(this.publicKey)).orElse(this.privateKey);
			engine.init(isEncrypt, cipher);
			return engine;
		} catch (Exception e) {
			throw new AsymmetricException(e.getMessage(), e);
		}
	}
	
	/**	
	 * 	初始化公钥/私钥或者密钥对
	 * 	<br>如果两者都不传递将生成密钥对
	 * 	<br>任意传递一个将进行还原公钥/私钥操作
	 * 	<pre>支持格式:
	 * 	原始Q值,D值方式
	 * 	X509(PublicKey,PrivateKey)
	 * 	OpenSSH方式
	 * 	</pre>
	 * 
	 * 	@author Pan
	 * 	@param 	publicKey	公钥
	 * 	@param 	privateKey	私钥
	 */
	void initializeKeys(byte[] publicKey, byte[] privateKey) {
		boolean empty = ValidParam.isEmpty(publicKey);
		boolean empty2 = ValidParam.isEmpty(privateKey);
		
		//	生成密钥对
		if (empty && empty2) {
			AsymmetricCipherKeyPair keyPair = ECKeyUtils.generateKeyPair(EC_DOMAIN_PARAMETERS);
			this.publicKey = ECKeyUtils.generatePublicKey(keyPair, EC_DOMAIN_PARAMETERS);
			this.privateKey = ECKeyUtils.generatePrivateKey(keyPair, EC_DOMAIN_PARAMETERS);
		}
		
		//	还原公钥/私钥操作
		if (!empty) {
			this.publicKey = ECKeyUtils.generatePublicKey(publicKey, EC_DOMAIN_PARAMETERS);
		}
		if (!empty2) {
			this.privateKey = ECKeyUtils.generatePrivateKey(publicKey, EC_DOMAIN_PARAMETERS);
		}
	}
	
	/**	
	 * 	获取SM2引擎
	 * 	
	 * 	@author Pan
	 * 	@param 	isEncrypt	true为获取加密引擎
	 * 	@return	SM2Engine
	 */
	private SM2Engine getEngine(boolean isEncrypt) {
		if (isEncrypt) {
			if (this.encryptEngine == null) {
				this.encryptEngine = initEngine(true);
			}	
			return this.encryptEngine;
		} else {
			if (this.decryptEngine == null) {
				this.decryptEngine = initEngine(false);
			}
			return this.decryptEngine;
		}
	}
	
	/**
	 * 	最终执行加解密方法
	 * 	
	 * 	@author Pan
	 * 	@param 	isEncrypt	true加密,false解密
	 * 	@param 	content		加解密内容
	 * 	@return	byte[]		密文
	 */
	public byte[] doFinal(boolean isEncrypt, byte[] content) {
		SM2Engine engine = getEngine(isEncrypt);
		try {
			return engine.processBlock(content, 0, content.length);
		} catch (Exception e) {
			throw new AsymmetricException(e.getMessage(), e);
		}
	}
	
	/**	
	 * 	获取公钥
	 * 	
	 * 	@author Pan
	 * 	@return	ECPublicKeyParameters
	 */
	public ECPublicKeyParameters getPublicKey() {
		if (this.publicKey == null) {
			return null;
		}
		return (ECPublicKeyParameters) this.publicKey;
	}
	
	/**	
	 * 	获取私钥
	 * 	
	 * 	@author Pan
	 * 	@return	ECPrivateKeyParameters
	 */
	public ECPrivateKeyParameters getPrivateKey() {
		if (this.privateKey == null) {
			return null;
		}
		return (ECPrivateKeyParameters) this.privateKey;
	}
	
	/**	
	 * 	获取公钥
	 * 	
	 * 	@author Pan
	 * 	@param  publicKey 公钥
	 * 	@return	ECPublicKeyParameters
	 */
	public ECPublicKeyParameters getPublicKey(byte[] publicKey) {
		if (this.publicKey == null) {
			this.publicKey = ECKeyUtils.generatePublicKey(publicKey, EC_DOMAIN_PARAMETERS);
		}
		return (ECPublicKeyParameters) this.publicKey;
	}
	
	/**	
	 * 	获取私钥
	 * 	
	 * 	@author Pan
	 * 	@param 	privateKey	私钥
	 * 	@return	ECPrivateKeyParameters
	 */
	public ECPrivateKeyParameters getPrivateKey(byte[] privateKey) {
		if (this.privateKey == null) {
			this.privateKey = ECKeyUtils.generatePrivateKey(privateKey, EC_DOMAIN_PARAMETERS);
		}
		return (ECPrivateKeyParameters) this.privateKey;
	}
	
	/**
	 * 	获取公钥Q值
	 * 	
	 * 	@author Pan
	 * 	@return	ECPoint
	 */
	public ECPoint getEcPoint() {
		if (this.publicKey == null) {
			return null;
		}
		return ((ECPublicKeyParameters) this.publicKey).getQ();
	}
	
	/**	
	 * 	获取公钥Q值
	 * 	
	 * 	@author Pan
	 * 	@return	byte[]
	 */
	public byte[] getQ() {
		if (this.publicKey == null) {
			return Empty.arrayByte();
		}
		return ((ECPublicKeyParameters) this.publicKey).getQ().getEncoded(false);
	}
	
	/**	
	 * 	获取私钥D值
	 * 	
	 * 	@author Pan
	 * 	@return	BigInteger
	 */
	public BigInteger getD() {
		if (this.privateKey == null) {
			return null;
		}
		return ((ECPrivateKeyParameters) this.privateKey).getD();
	}
	
	@Override
	public byte[] encrypt(byte[] content) {
		Assert.isNotEmpty(content);
		return doFinal(true, content);
	}
	
	@Override
	public byte[] encrypt(String content) {
		Assert.isNotEmpty(content);
		return doFinal(true, content.getBytes());
	}
	
	@Override
	public String encryptToHex(byte[] content) {
		Assert.isNotEmpty(content);
		return HexUtils.encode(doFinal(true, content));
	}
	
	@Override
	public String encryptToHex(byte[] content, boolean toLowerCase) {
		return HexUtils.encode(doFinal(true, content), toLowerCase);
	}
	
	@Override
	public String encryptToHex(String content) {
		Assert.isNotEmpty(content);
		return encryptToHex(content.getBytes());
	}
	
	@Override
	public String encryptToHex(String content, boolean toLowerCase) {
		Assert.isNotEmpty(content);
		return encryptToHex(content.getBytes(), toLowerCase);
	}
	
	@Override
	public byte[] encryptToBase64(byte[] content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encode(doFinal(true, content));
	}

	@Override
	public byte[] encryptToBase64(byte[] content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return Base64Utils.encode(doFinal(true, content), base64Type);
	}

	@Override
	public byte[] encryptToBase64(String content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encode(doFinal(true, content.getBytes()));
	}

	@Override
	public byte[] encryptToBase64(String content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return Base64Utils.encode(doFinal(true, content.getBytes()), base64Type);
	}

	@Override
	public String encryptToBase64Str(byte[] content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encodeToStr(doFinal(true, content));
	}

	@Override
	public String encryptToBase64Str(byte[] content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return Base64Utils.encodeToStr(doFinal(true, content), base64Type);
	}

	@Override
	public String encryptToBase64Str(String content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encodeToStr(doFinal(true, content.getBytes()));
	}

	@Override
	public String encryptToBase64Str(String content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return Base64Utils.encodeToStr(doFinal(true, content.getBytes()), base64Type);
	}

	@Override
	public byte[] decrypt(byte[] content) {
		Assert.isNotEmpty(content);
		return doFinal(false, content);
	}
	
	@Override
	public byte[] decryptFromHex(String content) {
		Assert.isNotEmpty(content);
		return doFinal(false, HexUtils.decode(content));
	}
	
	@Override
	public String decryptFromHexStr(String content) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromHex(content));
	}
	
	@Override
	public byte[] decryptFromBase64(byte[] content) {
		Assert.isNotEmpty(content);
		return doFinal(false, Base64Utils.decode(content));
	}
	
	@Override
	public byte[] decryptFromBase64(byte[] content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return doFinal(false, Base64Utils.decode(content, base64Type));
	}
	
	@Override
	public byte[] decryptFromBase64(String content) {
		Assert.isNotEmpty(content);
		return doFinal(false, Base64Utils.decode(content));
	}
	
	@Override
	public byte[] decryptFromBase64(String content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return doFinal(false, Base64Utils.decode(content, base64Type));
	}
	
	@Override
	public String decryptFromBase64Str(byte[] content) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content));
	}
	
	@Override
	public String decryptFromBase64Str(byte[] content, Base64Type base64Type) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content, base64Type));
	}
	
	@Override
	public String decryptFromBase64Str(String content) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content));
	}
	
	@Override
	public String decryptFromBase64Str(String content, Base64Type base64Type) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content, base64Type));
	}
	
	@Override
	public byte[] getPublicEncode() {
		if (this.publicKey == null) {
			return Empty.arrayByte();
		}
		return ECKeyUtils.encodeToBytes(this.publicKey);
	}
	
	@Override
	public String getPublicEncodeToHex() {
		if (this.publicKey == null) {
			return null;
		}
		return ECKeyUtils.encodeToHex(this.publicKey);
	}
	
	@Override
	public byte[] getPublicEncodeToBase64() {
		if (this.publicKey == null) {
			return Empty.arrayByte();
		}
		return ECKeyUtils.encodeToBase64(this.publicKey);
	}
	
	@Override
	public String getPublicEncodeToBase64Str() {
		if (this.publicKey == null) {
			return null;
		}
		return ECKeyUtils.encodeToBase64Str(this.publicKey);
	}
	
	@Override
	public byte[] getPrivateEncode() {
		if (this.privateKey == null) {
			return Empty.arrayByte();
		}
		return ECKeyUtils.encodeToBytes(this.privateKey);
	}
	
	@Override
	public String getPrivateEncodeToHex() {
		if (this.privateKey == null) {
			return null;
		}
		return ECKeyUtils.encodeToHex(this.privateKey);
	}
	
	@Override
	public byte[] getPrivateEncodeToBase64() {
		if (this.privateKey == null) {
			return Empty.arrayByte();
		}
		return ECKeyUtils.encodeToBase64(this.privateKey);
	}
	
	@Override
	public String getPrivateEncodeToBase64Str() {
		if (this.privateKey == null) {
			return null;
		}
		return ECKeyUtils.encodeToBase64Str(this.privateKey);
	}
	
	/**	
	 * 	创建Key密钥对对象(可用于缓存)
	 * 	<br>默认Base64编码格式
	 * 
	 * 	@author Pan
	 * 	@return	AsymmetricKey
	 */
	public AsymmetricKey createKeyObject() {
		return createKeyObject(true);
	}
	
	/**	
	 * 	创建Key密钥对对象(可用于缓存)
	 * 	<br>自定义编码格式(true:十六进制, false:Base64)
	 * 
	 * 	@author Pan
	 * 	@param 	isBase64	true:十六进制, false:Base64
	 * 	@return	AsymmetricKey
	 */
	public AsymmetricKey createKeyObject(boolean isBase64) {
		return new AsymmetricKey(getPublicEncode(), getPrivateEncode(), isBase64);
	}
}
